<?php
/**
 * @package contenite
 */

/**
 * @package contenite
 * Base class for application models. 
 * Generally used as (model > app_model > my_model) 
 */
class model {
  function __construct($data = array()) {
    foreach ($data as $name => $field) {
      $this->$name = $data[$name];
    }
  }
  
  function has_many($name) {
    if (!isset($this->_has_many[$name])) {
      error_log("has_many: $name not in model");
      return;
    }
    $def =& $this->_has_many[$name];
     
    $this->_fields[$name] = array(
      'widget' => 'many',
      'model' => $model = load($def['model']));
      
    $where[] = $def['foreign_key'] . ' = %s';
    $args[] = $this->{$def['foreign_key']};

    $where[] = array_shift($def['condition']);
    $args = array_merge($args, $def['condition']);

    $this->$name = $model->find(array(
      'where' => join(' AND ', $where),
      'args' => $args,
      'order' => $def['order']));
  }

  function del($id = NULL) {
    global $DB;
    
    $model = get_class($this);
    
    $key = $this->_key;
    if (!$id) {
      if (!isset($this->{$key})) {
        return FALSE;
      }
      $id = $this->{$key};
    }
    return $DB->query(
      "DELETE FROM {$model} WHERE {$key} = %s", $id);
  }    

  function _placeholder($field_name) {
    return stripos($this->_fields[$field_name]['type'], 'INT') === 0 ? '%d' : '%s';
  }

  function save($data) {
    global $DB;
    global $DEBUG;
    // $DEBUG = 9;
    
    $model = get_class($this);
    
    assert(isset($this->_key));
    $key_names = is_array($this->_key) ? $this->_key : array($this->_key);
    $create = FALSE;
    $key_set = array();
    $key_values = array();
    
    foreach ($key_names as $name) {
      if (isset($this->$name)) {
        $key_set[] = $name . ' = ' . $this->_placeholder($name);
        $key_values[] = $this->$name;
      }
      else {
        error_log(get_class() . '.save: ' . $name . ' not set => CREATE');
        $create = TRUE;
        break;
      }
    }
        
    $set = array();
    $values = array();
    foreach ($data as $n => $v) {
      if (!isset($this->_fields[$n])) {
        continue;
      }
      if (isset($this->_fields[$n]['sql'])) {
        $set[] = "$n = " . $this->_fields[$n]['sql'];
      }
      else {
        $set[] = $n . ' = ' . ($v === NULL ? 'NULL' : $this->_placeholder($n));
      }
      if ($v !== NULL) {
        $values[] = $v;
      }
    }

    if ($create) {
      if (isset($this->_fields['created'])) {
        $set[] = 'created = UNIX_TIMESTAMP()';
      }
      $status = $DB->vquery(
        "INSERT INTO {$model} SET " . join(', ', $set), $values);
      error_log("INSERT INTO {$model} SET " . join(', ', $set)
        . " :: " . serialize($values));
    }
    else {
      if (isset($this->_fields['updated'])) {
        $set[] = 'updated = UNIX_TIMESTAMP()';
      }
      $status = $DB->vquery(
        "UPDATE {$model} SET " . join(', ', $set) . " WHERE " 
        . join(' AND ', $key_set), array_merge($values, $key_values));
      error_log("UPDATE {$model} SET " . join(', ', $set) . " WHERE " 
        . join(' AND ', $key_set) 
        . " :: " . serialize(array_merge($values, $key_values)));
      return $status;
    }
    return $status;
  }
  
  private function setup($data, $construct = FALSE) {
    global $DB, $DEBUG;

    $class = get_class($this);
    $rs = $DB->query('DESCRIBE ' . $class);
    if ($rs) {  
      /*
      while ($field = $DB->fetch($rs)) {
        var_dump($field);
      }
      */
    }
    else {
      $fields = array();
      
      foreach ($this->_fields as $name => $field) {
        $fields[] = $name . ' ' . $field['type'];
      }
      if (!is_array($this->_key)) {
        $this->_key = array($this->_key);
      }
      $fields[] = 'PRIMARY KEY (' . join(', ', $this->_key) . ')';
      if (isset($this->_unique)) {
        foreach ($this->_unique as $key) {
          if (!is_array($key)) {
            $key = array($key);
          }
          $fields[] = 'UNIQUE KEY ' . join('_', $key)
            . '(' . join(', ', $key) . ')';
        }
      }
      
      $DEBUG = 9;
      $DB->create_table($class, join(",\n", $fields));
    }
  }
}

class model_generator {
  function __construct() {
    $class = get_class($this);
    assert(preg_match('/(\w+)_generator/', $class, $matches));
    $this->model = $matches[1];
  } 
  
  function model($data = array()) {
    $model = $this->model;
    return new $model($data);
  }

  function read($key) {
    $model = $this->model();
    return $this->one(array('where' => $model->_key . ' = %s', 'args' => array($key)));
  }

  function count($args) {
    if ($data = $this->find(array_merge($args, array('fields' => array('COUNT(*) AS n'))))) {
      return $data[0]['n'];
    }
    else {
      return NULL;
    }
  }

  function find($args) {
    global $DEBUG;
    //$DEBUG = 9;
    $model = $this->model;

    $fields = isset($args['fields']) ? join(',', $args['fields']) : '*';
    $joins = isset($args['joins']) ? $args['joins'] : '';
    $where = isset($args['where']) ? $args['where'] : '1=1';
    
    global $DB;
    $rs = $DB->vquery("SELECT {$fields} FROM {$model} {$joins} WHERE " 
      . $where 
      . (isset($args['order']) 
        ? ' ORDER BY ' . $args['order'] : '')
      . (isset($args['limit']) 
        ? ' LIMIT ' . $args['limit'] : ''), 
      isset($args['args']) ? $args['args'] : array());

    $result = array();
    if ($rs) {
      while ($data = $DB->fetch($rs)) {
        $result[] = $data;
      }
      return $result;
    }
    else {
      return array();
    }
  }
  
  function page($args) {
    global $DEBUG;
    // $DEBUG=9;

    if (isset($args['count'])) {
      $count = $this->find($args['count']);
      $total = $count[0]['n'];
    }
    else {
      $total = call_user_func(array($this, 'count'), $args);
    }
    
    $name = isset($args['name']) ? $args['name'] : 'page';
    
    $page = isset($_GET[$name]) ? intval($_GET[$name]) : 1;
    $num = isset($args['num']) ? $args['num'] : 5;
    
    $last = intval(($total - 1) / $num) + 1;
    $range1 = ($page - 1) * $num + 1;
    $range2 = $page * $num;
    $range2 = $range2 >= $total ? $total : $range2;
    
    // all values one-based
    $pager = array(
      'name' => $name, 
      'first' => $page > 1 ? 1 : NULL, 
      'prev' => $page > 1 ? $page - 1 : NULL,
      'next' => $page < $last ? $page + 1 : NULL,
      'last' => $page < $last ? $last : NULL,
      'range1' => $range1,
      'range2' => $range2,
    );
    
    $offs = ($page - 1) * $num;
    if ($offs >= $total) {
      return array(array(), $pager);
    }
    return array($this->find(array_merge($args, array(
      'limit' => "$offs, $num"))),
      $pager);
  }
  
  function one($args) {
    global $DEBUG;
    //$DEBUG=9;
    $args['limit'] = 1;
    $items = $this->find($args);
    return $items ? new $this->model($items[0]) : NULL;
  }
}
